package com.radar.task.cd;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.exceptions.ExceptionUtil;
import cn.hutool.core.io.FileUtil;
import com.radar.algorithm.MathCommon;
import com.radar.common.DateConstants;
import com.radar.common.ProductType;
import com.radar.visualize.RasterMap;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.filefilter.RegexFileFilter;
import org.meteoinfo.data.GridData;
import org.meteoinfo.global.Extent;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import ucar.ma2.DataType;
import ucar.nc2.Attribute;
import ucar.nc2.NetcdfFile;
import ucar.nc2.NetcdfFileWriter;
import ucar.nc2.Variable;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

@Component
@Slf4j
public class MosaicTask {

    @Value("${ncPath}")
    private String ncPath;

    @Value("${tmpPath}")
    private String tmpPath;

    @Value("${delayed}")
    private int delayed;

    //    @Scheduled(cron = "0 0/6 * * * ? ")

    /**
     * 覆盖代码注释
     */
    public void jobRun() {
        Date date = new Date();
        date = DateUtil.offsetHour(date, -8);
        date = DateUtil.offsetMinute(date, -delayed);
        List<File> files = getFiles(date);
        long time = System.currentTimeMillis();
        log.info("start mosaic radar stations : {} , date: {}", files.size(), DateUtil.format(date, DateConstants.DATE_TIME_FORMAT_YYYY_MM_DD_HH_MI));
        String[] split = "95,114,18,36".split(",");
        Extent extent = new Extent(Double.parseDouble(split[0]), Double.parseDouble(split[1]), Double.parseDouble(split[2]), Double.parseDouble(split[3]));

        GridData gridData = new GridData();
        double step = 0.01;
        gridData.xArray = new double[(int) ((extent.maxX - extent.minX) / step)];
        gridData.yArray = new double[(int) ((extent.maxY - extent.minY) / step)];
        IntStream.range(0, gridData.xArray.length).forEach(i -> gridData.xArray[i] = extent.minX + step * i);
        IntStream.range(0, gridData.yArray.length).forEach(i -> gridData.yArray[i] = extent.minY + step * i);
        gridData.data = new double[gridData.yArray.length][gridData.xArray.length];
        byte[][][] forecast = new byte[20][gridData.yArray.length][gridData.xArray.length];
        double[] alt = new double[]{500, 1000, 1500, 2000, 2500, 3000, 3500, 4000, 4500, 5000, 5500, 6000, 6500, 7000, 7500, 8000, 8500, 9000, 9500, 10000, 10500, 11000, 11500, 12000};
        byte[][][] cappi = new byte[alt.length][gridData.yArray.length][gridData.xArray.length];
        MathCommon.fill(gridData.data);
        MathCommon.fill(forecast);
        MathCommon.fill(cappi);

        List<String> stations = new ArrayList<>();
        for (File file : files) {
            mosaicItem(gridData, file.getAbsolutePath(), forecast, cappi);
            stations.add(file.getParentFile().getName());
        }
        String ncFile = ncPath + File.separator + DateUtil.format(date, DateConstants.DATE_FORMAT_YYYYMMDDHHMM) + ".nc";
        String outdir = ncPath + File.separator + DateUtil.format(date, DateConstants.DATE_FORMAT_YYYYMMDDHHMM) + File.separator;
        FileUtil.mkdir(outdir);
        try {
            writeNC(ncFile, stations, gridData, gridData.xArray, gridData.yArray, date, forecast, cappi, alt);
            split(outdir, stations, gridData, date, forecast, cappi, alt);
        } catch (Exception e) {
            log.error("write nc file error stations : {} , date: {}", files.size(), DateUtil.format(date, DateConstants.DATE_TIME_FORMAT_YYYY_MM_DD_HH_MI));
            e.printStackTrace();
        }
        log.info("end mosaic radar time: {}, stations : {} , date: {}", System.currentTimeMillis() - time, files.size(), DateUtil.format(date, DateConstants.DATE_TIME_FORMAT_YYYY_MM_DD_HH_MI));
    }

    /**
     * 覆盖代码注释
     * @param date
     * @return
     */
    public List<File> getFiles(Date date) {
        List<File> files = new ArrayList<>();
        try {
            String dir = tmpPath + File.separator + DateUtil.format(date, DateConstants.DATE_FORMAT_YYYYMMDD);
            File[] ls = FileUtil.ls(dir);
            for (File stationDir : ls) {
                List<File> collect = FileUtil.loopFiles(stationDir, new RegexFileFilter("^(\\d{12}).nc$")).stream().filter(f -> {
                    return Math.abs(date.getTime() - DateUtil.parse(f.getName().substring(0, 12), DateConstants.DATE_FORMAT_YYYYMMDDHHMM).getTime()) < 1000 * 60 * 12;
                }).collect(Collectors.toList());
                long min = Long.MAX_VALUE;
                File fs = null;
                for (File f : collect) {
                    if (Math.abs(date.getTime() - DateUtil.parse(f.getName().substring(0, 12), DateConstants.DATE_FORMAT_YYYYMMDDHHMM).getTime()) < min) {
                        min = Math.abs(date.getTime() - DateUtil.parse(f.getName().substring(0, 12), DateConstants.DATE_FORMAT_YYYYMMDDHHMM).getTime());
                        fs = f;
                    }
                }
                if (fs != null) {
                    files.add(fs);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return files;
    }

    /**
     * 覆盖代码注释
     * @param data
     * @param ncfile
     * @param forecastData
     * @param cappi
     */
    public static void mosaicItem(GridData data, String ncfile, byte[][][] forecastData, byte[][][] cappi) {
        try {
            NetcdfFile netcdfFile = NetcdfFile.open(ncfile);
            double[] xarray = (double[]) netcdfFile.findVariable("Longitude_" + ProductType.REF.name()).read().copyToNDJavaArray();
            double[] yarray = (double[]) netcdfFile.findVariable("Latitude_" + ProductType.REF.name()).read().copyToNDJavaArray();
            if (xarray[0] > data.xArray[data.xArray.length - 1] ||
                    yarray[0] > data.yArray[data.yArray.length - 1] ||
                    xarray[xarray.length - 1] < data.xArray[0] ||
                    yarray[yarray.length - 1] < data.yArray[0]) {
                return;
            }
            int min_y_index = yarray[0] < data.yArray[0] ? 0 : (int) ((yarray[0] - data.yArray[0]) / data.getYDelt());
            int max_y_index = yarray[yarray.length - 1] > data.yArray[data.yArray.length - 1] ? data.yArray.length - 1 : (int) ((yarray[yarray.length - 1] - data.yArray[0]) / data.getYDelt());
            int min_x_index = xarray[0] < data.xArray[0] ? 0 : (int) ((xarray[0] - data.xArray[0]) / data.getXDelt());
            int max_x_index = xarray[xarray.length - 1] > data.xArray[data.xArray.length - 1] ? data.xArray.length - 1 : (int) ((xarray[xarray.length - 1] - data.xArray[0]) / data.getXDelt());
            byte[][] cr = (byte[][]) netcdfFile.findVariable("CR").read().copyToNDJavaArray();
            float[][][] c = null;
            Variable variable = netcdfFile.findVariable("CR_FORECAST");
            if (variable != null) {
                c = (float[][][]) variable.read().copyToNDJavaArray();
            }

            byte[][][] ppi = null;
            Variable variable1 = netcdfFile.findVariable("CAPPI");
            if (variable1 != null) {
                ppi = (byte[][][]) variable1.read().copyToNDJavaArray();
            }
            double xstep = xarray[1] - xarray[0];
            double ystep = yarray[1] - yarray[0];
            for (int i = min_y_index; i < max_y_index; i++) {
                for (int j = min_x_index; j < max_x_index; j++) {
                    if (data.xArray[j] > xarray[0] && data.xArray[j] < xarray[xarray.length - 1] &&
                            data.yArray[i] > yarray[0] && data.yArray[i] < yarray[yarray.length - 1]) {
                        int xindex = (int) ((data.xArray[j] - xarray[0]) / xstep);
                        int yindex = (int) ((data.yArray[i] - yarray[0]) / ystep);
                        if (xindex < cr[0].length && yindex < cr.length && yindex > 0 && xindex > 0) {
                            double value = MathCommon.DecodeRef(cr[cr.length - 1 - yindex][xindex]);
                            if (value > data.data[data.yArray.length - 1 - i][j]) {
                                data.data[data.yArray.length - 1 - i][j] = value;
                            }
                            if (c != null) {
                                for (int f = 0; f < 20; f++) {
                                    double fV = c[f][cr.length - 1 - yindex][xindex];
                                    if (fV > MathCommon.DecodeRef(forecastData[f][data.yArray.length - 1 - i][j])) {
                                        forecastData[f][data.yArray.length - 1 - i][j] = MathCommon.CodeRef(fV);
                                    }
                                }
                            }
                            if (ppi != null) {
                                for (int f = 0; f < cappi.length; f++) {
                                    double fV = MathCommon.DecodeRef(ppi[f][cr.length - 1 - yindex][xindex]);
                                    if (fV > MathCommon.DecodeRef(cappi[f][data.yArray.length - 1 - i][j])) {
                                        cappi[f][data.yArray.length - 1 - i][j] = MathCommon.CodeRef(fV);
                                    }
                                }
                            }
                        }
                    }
                }
            }
            netcdfFile.close();
        } catch (Exception e) {
            log.error(ExceptionUtil.stacktraceToString(e));
        }
    }

    /**
     * 覆盖代码注释
     * @param ncFile
     * @param stations
     * @param crData
     * @param xarray
     * @param yarray
     * @param date
     * @param data
     * @param cappiData
     * @param altArray
     * @throws Exception
     */
    public void writeNC(String ncFile, List<String> stations, GridData crData, double[] xarray, double[] yarray, Date date, byte[][][] data, byte[][][] cappiData, double[] altArray) throws Exception {
        NetcdfFileWriter write = NetcdfFileWriter.createNew(NetcdfFileWriter.Version.netcdf4, ncFile);
        NetcdfFile netcdfFile = null;
        if (write.isDefineMode()) {
            write.create();
            netcdfFile = write.getNetcdfFile();
        }
        // add global attriubte
        List<Attribute> globalAttr = new ArrayList<>();
        globalAttr.add(new Attribute("title", "MOSAIC RADAR FORECAST STRUCT"));

        globalAttr.add(new Attribute("fileName", new File(ncFile).getName()));
        Attribute references = new Attribute("references", "https://github.com/CyanideCN/PyCINRAD");
        globalAttr.add(references);
        globalAttr.add(new Attribute("description", ""));
        String station = "";
        for (String s : stations) {
            station += s + ",";
        }
        if (stations.size() > 0) {
            station = station.substring(0, station.length() - 1);
        }
        globalAttr.add(new Attribute("mosaic_stations", station));
        globalAttr.add(new Attribute("scan_time", DateUtil.format(date, DateConstants.DATE_TIME_FORMAT_YYYY_MM_DD_HH_MI)));
        for (Attribute attribute : globalAttr) {
            netcdfFile.addAttribute(netcdfFile.getRootGroup(), attribute);
        }
        write.setRedefineMode(true);
        // add lon lat alt Dimension
        write.addDimension(null, "Longitude", xarray.length);
        write.addDimension(null, "Latitude", yarray.length);
        write.addDimension(null, "Times", 20);
        write.addDimension(null, "Altitude", altArray.length);

        Variable v1 = write.addVariable(null, "Longitude", DataType.DOUBLE, "Longitude");
        v1.addAttribute(new Attribute("units", "degrees_east"));
        v1.addAttribute(new Attribute("actual_range", Arrays.asList(xarray[0], xarray[xarray.length - 1])));

        Variable v2 = write.addVariable(null, "Latitude", DataType.DOUBLE, "Latitude");
        v2.addAttribute(new Attribute("units", "degrees_north"));
        v2.addAttribute(new Attribute("actual_range", Arrays.asList(yarray[0], yarray[yarray.length - 1])));

        Variable v3 = write.addVariable(null, "Times", DataType.DOUBLE, "Times");
        v3.addAttribute(new Attribute("units", "minute"));
        v3.addAttribute(new Attribute("actual_range", Arrays.asList(6, 6 * 20)));

        Variable v4 = write.addVariable(null, "Altitude", DataType.DOUBLE, "Altitude");
        v4.addAttribute(new Attribute("units", "m"));
        v4.addAttribute(new Attribute("actual_range", Arrays.asList(altArray[0], altArray[altArray.length - 1])));


        Variable forecast = write.addVariable(null, "CR_FORECAST", DataType.BYTE, Arrays.asList(
                netcdfFile.findDimension("Times"),
                netcdfFile.findDimension("Latitude"),
                netcdfFile.findDimension("Longitude")));
        forecast.addAttribute(new Attribute("missing_value", (byte) -128));
        forecast.addAttribute(new Attribute("range_value", (byte) -100));
        forecast.addAttribute(new Attribute("add_offset", 32.5f));
        forecast.addAttribute(new Attribute("scale_factor", 0.5f));
        forecast.addAttribute(new Attribute("full_name", ProductType.REF.getFullName()));
        forecast.addAttribute(new Attribute("units", ProductType.REF.getUnit()));

        Variable cr = write.addVariable(null, "CR", DataType.BYTE, Arrays.asList(
                netcdfFile.findDimension("Latitude"),
                netcdfFile.findDimension("Longitude")));
        cr.addAttribute(new Attribute("missing_value", (byte) -128));
        cr.addAttribute(new Attribute("range_value", (byte) -100));
        cr.addAttribute(new Attribute("add_offset", 32.5f));
        cr.addAttribute(new Attribute("scale_factor", 0.5f));
        cr.addAttribute(new Attribute("full_name", ProductType.REF.getFullName()));
        cr.addAttribute(new Attribute("units", ProductType.REF.getUnit()));

        Variable cappi = write.addVariable(null, "CAPPI", DataType.BYTE, Arrays.asList(
                netcdfFile.findDimension("Altitude"),
                netcdfFile.findDimension("Latitude"),
                netcdfFile.findDimension("Longitude")));
        cappi.addAttribute(new Attribute("missing_value", (byte) -128));
        cappi.addAttribute(new Attribute("range_value", (byte) -100));
        cappi.addAttribute(new Attribute("add_offset", 32.5f));
        cappi.addAttribute(new Attribute("scale_factor", 0.5f));
        cappi.addAttribute(new Attribute("full_name", ProductType.REF.getFullName()));
        cappi.addAttribute(new Attribute("units", ProductType.REF.getUnit()));

        write.setRedefineMode(false);

        write.write(v1, MathCommon.toArray(xarray));
        write.write(v2, MathCommon.toArray(yarray));
        write.write(v3, MathCommon.toArray(new double[]{6, 12, 18, 24, 30, 36, 42, 48, 54, 60, 66, 72, 78, 84, 90, 96, 102, 108, 112, 120}));
        write.write(v4, MathCommon.toArray(altArray));

        // write byte data
        write.write(forecast, MathCommon.toByteArray(data));
        write.write(cr, MathCommon.toByteArray(MathCommon.Code(crData.data, ProductType.REF)));
        write.write(cappi, MathCommon.toByteArray(cappiData));

        netcdfFile.close();
        write.abort();
        write.close();
    }

    /**
     * 覆盖代码注释
     * @param dir
     * @param stations
     * @param crData
     * @param date
     * @param data
     * @param cappiData
     * @param altArray
     * @throws IOException
     */
    public void split(String dir, List<String> stations, GridData crData, Date date, byte[][][] data, byte[][][] cappiData, double[] altArray) throws IOException {
        // write cr
        String dt = DateUtil.format(date, DateConstants.DATE_FORMAT_YYYYMMDDHHMM);
        RasterMap rasterMap = new RasterMap(null);
        BufferedImage bufferedImage = rasterMap.drawSingleRaster(crData.data, new Color(0, 0, 0, 0));
        ImageIO.write(bufferedImage, "png", new File(dir + File.separator + dt + "_CR.png"));
        FileUtil.writeBytes(BitConverter.getBytes(crData.data), dir + File.separator + dt + "_CR.dat");

        // write forecast
        for (int i = 0; i < data.length; i++) {
            bufferedImage = rasterMap.drawSingleRaster(MathCommon.decode(data[i], ProductType.REF), new Color(0, 0, 0, 0));
            ImageIO.write(bufferedImage, "png", new File(dir + File.separator + dt + "_CR_" + ((i + 1) < 10 ? "0" + (i + 1) : (i + 1)) + ".png"));
            FileUtil.writeBytes(BitConverter.getBytes(MathCommon.decode(data[i], ProductType.REF)), dir + File.separator + dt + "_CR_" + ((i + 1) < 10 ? "0" + (i + 1) : (i + 1)) + ".dat");
        }
        // write cappi
        for (int i = 0; i < cappiData.length; i++) {
            bufferedImage = rasterMap.drawSingleRaster(MathCommon.decode(cappiData[i], ProductType.REF), new Color(0, 0, 0, 0));
            ImageIO.write(bufferedImage, "png", new File(dir + File.separator + dt + "_CAPPI_" + (int) altArray[i] + ".png"));
            FileUtil.writeBytes(BitConverter.getBytes(MathCommon.decode(cappiData[i], ProductType.REF)), dir + File.separator + dt + "_CAPPI_" + (int) altArray[i] + ".dat");
        }

        String station = "";
        for (String s : stations) {
            station += s + ",";
        }
        if (stations.size() > 0) {
            station = station.substring(0, station.length() - 1);
        }
        FileUtil.writeString(station, dir + File.separator + dt + "_mosaic_stations.txt", Charset.defaultCharset());

    }

//    public static void main(String[] args) throws Exception {
//        MosaicTask mosaicTask = new MosaicTask();
//        mosaicTask.tmpPath = "E:\\data\\radar\\tmp";
//        mosaicTask.ncPath = "E:\\data\\radar\\nc";
//        mosaicTask.jobRun();
//    }

    public static void main(String[] args) throws IOException {
        String date = "202107302300";
        String ncFile = "C:\\Users\\lenovo\\Desktop\\cr\\" + date + ".nc";
        NetcdfFile open = NetcdfFile.open(ncFile);
        double[] alt = new double[]{500, 1000, 1500, 2000, 2500, 3000, 3500, 4000, 4500, 5000, 5500, 6000, 6500, 7000, 7500, 8000, 8500, 9000, 9500, 10000, 10500, 11000, 11500, 12000};
        byte[][][] data = (byte[][][]) open.findVariable("CAPPI").read().copyToNDJavaArray();
        byte[][] crdata = (byte[][]) open.findVariable("CR").read().copyToNDJavaArray();
        FileUtil.mkdir("C:\\Users\\lenovo\\Desktop\\cr\\" + date);
        RasterMap rasterMap = new RasterMap(null);
        BufferedImage bufferedImage = rasterMap.drawSingleRaster(MathCommon.decode(crdata, ProductType.REF), new Color(0, 0, 0, 0));
        ImageIO.write(bufferedImage, "png", new File("C:\\Users\\lenovo\\Desktop\\cr\\" + date + "\\" + date + "_CR.png"));

        for (int i = 0; i < 24; i++) {
            rasterMap = new RasterMap(null);
            bufferedImage = rasterMap.drawSingleRaster(MathCommon.decode(data[i], ProductType.REF), new Color(0, 0, 0, 0));
            ImageIO.write(bufferedImage, "png", new File("C:\\Users\\lenovo\\Desktop\\cr\\" + date + "\\" + date + "_" + alt[i] + ".png"));
        }
    }
}
